--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit db8a58536a8024e64af9fd60e8ec8f1532fd50dc
Parents : 7023a7a
Author : Ivan <ivan@quad4.io>
Signature : Invalid signer <e46112d44649266d71fe2193e00a4710>, author is <ivan@quad4.io>
Date : 2026-07-10T00:57:48-05:00
docs(agents): add agent conventions and skills documentation
Changes
23 files changed, 1319 insertions(+), 0 deletions(-)
Diff
diff --git a/docs/agents/README.md b/docs/agents/README.md
new file mode 100644
index 00000000..c5fe9d46
--- /dev/null
+++ b/docs/agents/README.md
@@ -0,0 +1,79 @@
+# Agent guidance for MeshChatX
+
+Neutral, tool-agnostic notes for automated agents and human contributors who work like agents.
+This tree is **not** part of the in-app user documentation. End-user docs live under `docs/en/`.
+
+## Start here
+
+1. Read [overview.md](overview.md) for architecture, storage, security, env vars, and invariants.
+2. Read [conventions/reticulum-zen.md](conventions/reticulum-zen.md) before any mesh-facing design or code.
+3. Apply [conventions/](conventions/) for the surface you are editing.
+4. Open a [skills/](skills/) guide when the task matches that workflow.
+
+Root [AGENTS.md](../../AGENTS.md) is a short pointer to this directory.
+Cursor rules under `.cursor/rules/`:
+
+- Always on: `meshchatx-core.mdc`, `reticulum-zen-gates.mdc`
+- Globs: `meshchatx-backend.mdc`, `meshchatx-frontend.mdc`, `meshchatx-android.mdc`, `meshchatx-tests.mdc`
+
+## Layout
+
+| Path | Purpose |
+| ------------------------------------------------------------ | ------------------------------------- |
+| [overview.md](overview.md) | Project brief and critical invariants |
+| [conventions/reticulum-zen.md](conventions/reticulum-zen.md) | Zen of Reticulum hard gates |
+| [conventions/core.md](conventions/core.md) | Always-on standards |
+| [conventions/frontend.md](conventions/frontend.md) | Vue UI |
+| [conventions/backend.md](conventions/backend.md) | Python / HTTP / SQLite |
+| [conventions/android.md](conventions/android.md) | Android WebView bridge |
+| [conventions/tests.md](conventions/tests.md) | Test placement and verification |
+
+## Skills
+
+### UI and wiring
+
+| Skill | Use when |
+| ------------------------------------------------------------------ | ------------------------------------------------------ |
+| [page-toast-tests](skills/page-toast-tests/SKILL.md) | New pages, toasts, i18n, tests |
+| [contribution-registries](skills/contribution-registries/SKILL.md) | Nav, tools, commands, settings search, WS event wiring |
+
+### Identity and data
+
+| Skill | Use when |
+| -------------------------------------------------------------------------- | ------------------------------------------------- |
+| [identity-restore](skills/identity-restore/SKILL.md) | Identity key vs database zip restore |
+| [identity-switch-teardown](skills/identity-switch-teardown/SKILL.md) | Live identity switch without cross-identity leaks |
+| [database-migrations-backups](skills/database-migrations-backups/SKILL.md) | Schema bumps, backups, snapshots, restore safety |
+| [landlock-sqlite](skills/landlock-sqlite/SKILL.md) | Landlock + SQLite conversation failures |
+
+### Security and plugins
+
+| Skill | Use when |
+| ------------------------------------------------------------------ | --------------------------------------------- |
+| [reticulum-design-gates](skills/reticulum-design-gates/SKILL.md) | Mesh design review against Zen of Reticulum |
+| [auth-csrf-ws-security](skills/auth-csrf-ws-security/SKILL.md) | CSRF, auth, WS mutator denylist |
+| [plugin-install-security](skills/plugin-install-security/SKILL.md) | Plugin install, RSG, permissions, integrity |
+| [rns-link-api](skills/rns-link-api/SKILL.md) | Generic RNS Link WebSocket / plugin transport |
+
+### Platforms and boot
+
+| Skill | Use when |
+| ---------------------------------------------------------------------- | ------------------------------------------------- |
+| [deferred-network-startup](skills/deferred-network-startup/SKILL.md) | HTTP-up vs RNS-ready, status, 503s, RNS panic |
+| [electron-frozen-packaging](skills/electron-frozen-packaging/SKILL.md) | Frozen desktop spawn, loading, crash recovery |
+| [android-webview-bridge](skills/android-webview-bridge/SKILL.md) | Android chooser MIME, storage, WebView navigation |
+
+### Verification
+
+| Skill | Use when |
+| -------------------------------------- | ---------------------------------------- |
+| [test-loop](skills/test-loop/SKILL.md) | Focused verification without hung shells |
+
+## Product docs (users)
+
+- `docs/en/architecture.md`
+- `docs/en/identity-and-security.md`
+- `docs/en/getting-started.md`
+- `docs/en/rns-link-api.md`
+- `docs/en/platform-guides/linux-sandbox.md`
+- `CONTRIBUTING.md`
diff --git a/docs/agents/conventions/android.md b/docs/agents/conventions/android.md
new file mode 100644
index 00000000..d79ecaf1
--- /dev/null
+++ b/docs/agents/conventions/android.md
@@ -0,0 +1,8 @@
+# Android conventions
+
+Applies when editing `android/**/*.{java,kt}`.
+
+- WebView `accept` extension tokens are not valid MIME types. Map `.ext` to `application/octet-stream` / `*/*` before `EXTRA_MIME_TYPES`.
+- Set `EXTRA_ALLOW_MULTIPLE` only when the chooser mode is multi-open.
+- Prefer existing bridge patterns in `MainActivity` for storage, file pick, and push.
+- After Android bridge changes, note whether emulator smoke or unit coverage is needed.
diff --git a/docs/agents/conventions/backend.md b/docs/agents/conventions/backend.md
new file mode 100644
index 00000000..4cb24f0a
--- /dev/null
+++ b/docs/agents/conventions/backend.md
@@ -0,0 +1,11 @@
+# Backend conventions
+
+Applies when editing `meshchatx/**/*.py`.
+
+- Prefer `uv run` / `task` for pytest and ruff.
+- HTTP handlers: return 400 for `ValueError` / bad input, 503 for retryable SQLite/Landlock unavailability, 500 only for unexpected failures.
+- Multipart parsers must not assume field order.
+- SQLite worker connections must set `temp_store=MEMORY` in `DatabaseProvider` (Landlock-safe).
+- Under Landlock, memory-pressure must not force FILE temp for conversation queries.
+- Keep conversation list queries slim: truncate content, derive attachment flags in SQL, avoid shipping full `fields` blobs.
+- Identity restore validates size and empty payloads. Preserve existing identity metadata on re-import.
diff --git a/docs/agents/conventions/core.md b/docs/agents/conventions/core.md
new file mode 100644
index 00000000..25e1e943
--- /dev/null
+++ b/docs/agents/conventions/core.md
@@ -0,0 +1,10 @@
+# Core conventions
+
+- Read `docs/agents/overview.md` for layout, commands, and domain traps.
+- Mesh-facing work: read `docs/agents/conventions/reticulum-zen.md` and run `docs/agents/skills/reticulum-design-gates/SKILL.md` gates first.
+- Prefer `task` targets (`format`, `lint`, `test:quick`, `test:backend`, `test:frontend`).
+- Minimal diffs. Match nearby style. Keep SPDX headers on new project files (`0BSD` unless file already differs).
+- No emojis in repo text. No TODO/FIXME comment noise.
+- Do not commit/push unless asked.
+- User-visible UI strings: i18n keys. Action feedback: `ToastUtils`.
+- Do not invent install/run flows when Taskfile already covers them.
diff --git a/docs/agents/conventions/frontend.md b/docs/agents/conventions/frontend.md
new file mode 100644
index 00000000..a65f9a70
--- /dev/null
+++ b/docs/agents/conventions/frontend.md
@@ -0,0 +1,11 @@
+# Frontend conventions
+
+Applies when editing `meshchatx/src/frontend/**/*.{vue,js}`.
+
+- Vue 3 Options API is the dominant pattern. Match the file you edit.
+- API calls go through `window.api` (not ad-hoc axios imports in pages).
+- Toasts: `ToastUtils.success|error|warning|info|loading|dismiss`.
+- New top-level pages need: route in `main.js`, nav entry when discoverable, `en.json` keys, frontend tests.
+- Do not use `_`-prefixed keys in Vue `data()` (`vue/no-reserved-keys`).
+- File inputs: prefer broad `accept` for identity keys (`.bin,.key,.identity,application/octet-stream,*/*`). Database restore stays `.zip`.
+- Prefer existing MaterialDesignIcon / layout patterns over new design systems.
diff --git a/docs/agents/conventions/reticulum-zen.md b/docs/agents/conventions/reticulum-zen.md
new file mode 100644
index 00000000..4c36e7b2
--- /dev/null
+++ b/docs/agents/conventions/reticulum-zen.md
@@ -0,0 +1,58 @@
+# Reticulum Zen conventions
+
+Source philosophy: [Zen of Reticulum](https://reticulum.network/manual/zen.html).
+This file turns that philosophy into hard gates for MeshChatX work.
+Full checklist: `docs/agents/skills/reticulum-design-gates/SKILL.md`.
+
+## Mental model (required)
+
+- There is no cloud center. Peers inhabit a fabric. Do not design features that need a privileged server, registry, or landlord API to function on the mesh.
+- Destination hashes are identity, not location. Do not bind mesh reachability to IP, hostname, DNS, or a fixed interface.
+- Assume every link and peer is hostile. Encryption and cryptographic proof are not optional add-ons.
+- Bandwidth and airtime are scarce. Prefer small payloads, async delivery, and store-and-forward over chatty request/response.
+- Interfaces are clothing. Application code talks to destinations and aspects, not to WiFi vs LoRa vs TCP specifics.
+- Announces are presence. Do not invent a central directory when announce + path discovery already solve discovery.
+- Tools have ethics. Do not add surveillance, extraction, kill-chain, or identity-tracking features that fight local-first sovereignty.
+
+## Hard no
+
+1. Do not require clearnet HTTP/SaaS for core mesh messaging, identity, or pathfinding.
+2. Do not treat LXMF / RNS as "just another WebSocket API" that must stay online synchronously.
+3. Do not store or ship full identity private keys in logs, bug reports, UI dumps, or plugin storage without explicit user action and redaction defaults.
+4. Do not invent new global registries that map people to locations or force a single naming authority.
+5. Do not add plaintext mesh protocols "for convenience".
+6. Do not block the UI forever waiting for a path. Queue, retry, propagate, or fail with a recoverable state.
+7. Do not couple feature logic to one physical medium or one interface type.
+8. Do not weaken plugin permission, RSG, CSRF, or auth gates to "make demos easier".
+
+## Hard yes
+
+1. Address peers by destination / identity hash. User-facing names are local labels on a keyring.
+2. Use aspects correctly (`lxmf.delivery`, `lxst.telephony`, `rrc.hub`, custom app aspects). Do not overload `lxmf.delivery` for non-LXMF apps.
+3. Design for intermittent links: send, continue, handle delivery later.
+4. Keep mesh payloads minimal. Truncate previews. Compress or chunk large transfers with existing tools (RNCP, attachments) instead of stuffing megabytes into chat fields.
+5. Prefer existing RNS / LXMF / LXST primitives over bespoke transports.
+6. Keep identity-scoped state inside `IdentityContext`. No cross-identity leakage.
+7. Privacy mode and Landlock constraints stay intact. Clearnet fetches remain opt-in and gated.
+
+## MeshChatX mapping
+
+| Zen idea | MeshChatX reality |
+| ------------------- | ----------------------------------------------------------------------- |
+| Portable identity | `storage/identities/<hash>/`, identity switch teardown |
+| Announce presence | announce handlers, favourites, path table |
+| Store and forward | LXMF propagation nodes, outbound delivery states |
+| Transport agnostic | Reticulum interfaces config, not app-level socket code |
+| Scarcity | slim conversation queries, stamp costs, attachment discipline |
+| Cryptographic trust | destination recall, proofs, HTTPS local UI is separate from mesh crypto |
+
+## Before you ship mesh-facing code
+
+Answer all of these. If any answer is wrong, redesign.
+
+1. Does this still work with no clearnet and only Reticulum interfaces up?
+2. Does it address a destination hash / aspect, not an IP or URL, for mesh peers?
+3. Can it tolerate minutes of delay or a missing path without corrupting state?
+4. Is the wire payload as small as the intent allows?
+5. Would a hostile transport node learn nothing useful beyond ciphertext and routing proofs?
+6. Is identity material and personal metadata default-redacted in any export or bug path?
diff --git a/docs/agents/conventions/tests.md b/docs/agents/conventions/tests.md
new file mode 100644
index 00000000..ea023591
--- /dev/null
+++ b/docs/agents/conventions/tests.md
@@ -0,0 +1,10 @@
+# Test conventions
+
+Applies when editing `tests/**/*.{py,js}`.
+
+- Backend: `tests/backend/test_*.py` with pytest + asyncio auto mode.
+- Frontend: `tests/frontend/*.test.js` with vitest + `@vue/test-utils`.
+- Mock `window.api` for page tests. Assert toasts when outcomes are user-visible.
+- Prefer focused files over full suite unless the user asks for broad runs.
+- Landlock tests that apply the sandbox must run in a subprocess (one restrict per process).
+- Long-running / notification soak suites can hang. Prefer timeouts and avoid piping pytest through `tail` in agent shells.
diff --git a/docs/agents/overview.md b/docs/agents/overview.md
new file mode 100644
index 00000000..d0266d8e
--- /dev/null
+++ b/docs/agents/overview.md
@@ -0,0 +1,332 @@
+# MeshChatX agent overview
+
+Project brief for automated agents and contributors.
+Conventions and task skills live under `docs/agents/`. This file is the durable source of truth for architecture and invariants.
+
+## What this project is
+
+Reticulum MeshChatX is a local-first mesh communications client on the Reticulum Network Stack.
+It is an independent fork of Reticulum MeshChat and is not affiliated with the upstream project.
+
+Core protocols:
+
+- **Reticulum (RNS)** - identities, paths, interfaces, encrypted transport
+- **LXMF** - messaging, attachments, propagation nodes
+- **LXST** - audio calls and telephony
+
+One Python process owns the web server, Reticulum stack, and per-identity managers.
+The Vue frontend is static assets served from `meshchatx/public/` after a Vite build.
+Electron and Android wrap the same backend.
+
+Website: [meshchatx.com](https://meshchatx.com)
+Source: [github.com/Quad4-Software/MeshChatX](https://github.com/Quad4-Software/MeshChatX)
+
+## Design goals (do not violate casually)
+
+- Local-first. Works on desktop, mobile, containers, and SBCs.
+- Preserve Reticulum / LXMF / LXST semantics while improving UX and ops tooling.
+- Multiple identities in one process without cross-identity data leakage.
+- Python backend and Vue frontend independently testable.
+- Predictable SQLite behaviour in constrained environments.
+- Prefer identity-scoped state, explicit migrations, and narrowly declared plugin permissions.
+
+## Reticulum Zen gates (mesh work)
+
+MeshChatX sits on Reticulum. Agents must not invent cloud-era or IP-era designs for mesh features.
+
+- Philosophy: [Zen of Reticulum](https://reticulum.network/manual/zen.html)
+- Conventions: `docs/agents/conventions/reticulum-zen.md`
+- Checklist skill: `docs/agents/skills/reticulum-design-gates/SKILL.md`
+- Cursor always-on rule: `.cursor/rules/reticulum-zen-gates.mdc`
+
+Short form: no mandatory cloud center, address destination hashes, assume hostile links, design for scarcity and delay, keep code transport-agnostic, keep identity state scoped.
+
+## Runtime shape
+
+```
+Browser / Electron / Android WebView
+ |
+ | HTTPS REST /api/v1/* and WSS /ws (+ /ws/telephone/audio)
+ v
+ReticulumMeshChat (meshchatx/meshchat.py)
+ |
+ +-- HTTP routes and static public/
+ +-- WebSocket event fan-out
+ +-- IdentityContext (active identity only)
+ | +-- SQLite (database.db under identity storage)
+ | +-- LXMRouter and message managers
+ | +-- TelephoneManager (LXST)
+ | +-- Domain managers (map, docs, RRC, bots, ...)
+ +-- Shared Reticulum instance (default ~/.reticulum)
+```
+
+Critical lifecycle facts:
+
+- HTTP can bind before RNS/identity finish starting. `/api/v1/status` reports `starting` / `ok` / `failed` with `stage` and `network_ready`.
+- CLI one-shots (`--self-check`, backup/restore helpers) still initialize synchronously.
+- Switching identities tears down the old `IdentityContext` and loads another. Do not stash identity-specific state in process globals.
+
+## Repository layout
+
+| Path | Role |
+| ------------------------- | ----------------------------------------- |
+| `meshchatx/meshchat.py` | Orchestration, HTTP/WS routes, CLI |
+| `meshchatx/src/backend/` | Managers, DB, security, Landlock, plugins |
+| `meshchatx/src/frontend/` | Vue 3 UI, locales, registries, helpers |
+| `meshchatx/public/` | Built frontend assets consumed at runtime |
+| `electron/` | Desktop shell around local HTTPS backend |
+| `android/` | WebView + Chaquopy Python bridge |
+| `tests/backend/` | pytest |
+| `tests/frontend/` | vitest |
+| `tests/e2e/` | Playwright |
+| `docs/en/` | In-app / shipped English docs |
+| `vendor/` | Vendored deps (for example LXMFy) |
+| `Taskfile.yml` | Preferred command entrypoints |
+| `docs/agents/` | Agent guidance (this tree) |
+| `AGENTS.md` | Short pointer to `docs/agents/` |
+
+Business rules belong in backend managers under `meshchatx/src/backend/`.
+Keep `meshchat.py` focused on transport and lifecycle when possible.
+
+## Tooling and versions
+
+- Python `>=3.11` (CI commonly runs 3.14)
+- Node.js `>=24`, pnpm from `package.json` `packageManager`
+- UV for Python deps
+- Task for common workflows
+
+Prefer Task targets over inventing one-off scripts:
+
+```bash
+task install
+task format
+task lint
+task test:quick
+task test:backend
+task test:frontend
+task test:e2e
+task run
+task dev
+```
+
+Useful focused commands:
+
+```bash
+uv run pytest tests/backend/test_<name>.py -q --tb=short
+pnpm exec vitest run tests/frontend/<Name>.test.js
+pnpm exec eslint <file> --fix
+uv run python -m meshchatx.meshchat --self-check
+```
+
+## Storage and identity model
+
+Default storage root: `./storage` (override with `--storage-dir` / `MESHCHAT_STORAGE_DIR`).
+Android may prefer external app files storage.
+
+Per identity:
+
+```
+storage/identities/<identity_hash>/
+ identity # private key bytes
+ metadata.json # display name, icon, cached addresses
+ database.db # SQLite (WAL files may exist)
+ database-backups/ # zip backups
+ snapshots/ # named snapshots
+ ssl/ # per-identity cert/key when using defaults
+ ... # LXMF dirs, caches, sqlite-tmp, etc.
+```
+
+Shared outside identity storage:
+
+- Reticulum config: `~/.reticulum` by default (`--reticulum-config-dir` / `MESHCHAT_RETICULUM_CONFIG_DIR`)
+- Interfaces and transport settings live with Reticulum, not only in the identity DB
+
+### Identity key restore vs database restore
+
+These are different operations. Do not conflate them in UI copy or code paths.
+
+| Goal | Where | Artifact / API |
+| --------------------------------------------- | ---------------------------------- | ------------------------------------------------------ |
+| Restore private key only | Tutorial step 2, Identities import | `POST /api/v1/identity/restore` |
+| Restore LXMF history, settings, identity tree | About → Restore from File, CLI | `POST /api/v1/database/restore`, `--restore-db` `.zip` |
+
+Identity export download should use a real extension such as `identity.bin`.
+File pickers for keys should accept `.bin`, `.key`, `.identity`, `application/octet-stream`, and `*/*`.
+Database restore pickers stay `.zip`.
+
+## Persistence rules
+
+- Engine: SQLite with explicit SQL and versioned migrations (no ORM).
+- Schema changes go through migrations in the database schema layer.
+- Backups and snapshots are first-class recovery tools. Prefer restore APIs over hand-editing DB files.
+- Conversation list / sidebar queries must stay slim. Truncate content previews. Derive attachment flags in SQL. Do not ship multi-MB `fields` blobs in list endpoints.
+- Worker-thread DB connections must apply the same pragmas as the main connection via `DatabaseProvider` (especially `temp_store`).
+
+## Landlock and Linux sandboxing
+
+On Linux, MeshChatX can apply a Landlock filesystem sandbox after startup.
+Control with `MESHCHAT_LANDLOCK` (`1` force on, `0` force off, unset = auto when kernel supports it).
+
+Critical SQLite interaction:
+
+- Under Landlock, `PRAGMA temp_store=FILE` can break complex conversation queries with `unable to open database file`.
+- Default worker connections to `temp_store=MEMORY`.
+- Memory-pressure mode may shrink cache/mmap. While Landlock is active, keep MEMORY temp.
+- Without Landlock, FILE temp plus a storage-local `sqlite-tmp` TMPDIR is acceptable.
+
+Landlock apply is process-wide and one-shot. Tests that enable it must run in a subprocess.
+
+Also see `docs/en/platform-guides/linux-sandbox.md` for Firejail / Bubblewrap host examples.
+
+## Security model (critical)
+
+Defaults aim at secure local operation:
+
+- HTTPS and WSS on by default (self-signed certs per identity when custom PEMs absent)
+- Optional HTTP auth (`--auth` / `MESHCHAT_AUTH=true`)
+- CSRF on mutating HTTP requests
+- Encrypted session cookies
+- CORS / CSP / defensive HTTP middleware
+- Access-attempt logging and lockout when auth is enabled
+- IP allowlisting available via app security settings
+- Privacy mode can block outbound clearnet HTTP from app features (does not stop Reticulum mesh traffic)
+
+Do not recommend exposing MeshChatX directly on the public internet without extra hardening.
+Prefer bind `127.0.0.1`, HTTPS, and auth if other local users share the host.
+
+Sensitive config changes (for example auth enable / password hash) must use CSRF-protected HTTP endpoints, not unrestricted WebSocket mutators.
+
+Password reset: `--reset-password` or `MESHCHAT_RESET_PASSWORD=true` clears the stored hash so a new password can be set in the UI.
+
+### Plugins
+
+Plugins are powerful and partially sandboxed. Treat install/enable paths as security-sensitive.
+
+- Frontend plugins run in Workers with capability grants
+- Backend WASM plugins use wasmtime with fuel / capability gates
+- Backend Python plugins and Sideband loaders are higher risk and permission-gated / danger-switched
+- Invalid RSG signatures hard-block install
+- Tampered installed trees should disable as integrity failures
+- Disable all plugins with `--disable-plugins` / `MESHCHAT_DISABLE_PLUGINS=true`
+
+## HTTP and WebSocket surface
+
+- REST under `/api/v1/*`
+- Frontend uses `window.api` / `apiClient.js` with CSRF on mutating calls
+- WebSocket `/ws` for live events (messages, identity switch, telephone, RRC, Nomad downloads, plugins, RNS link events)
+- Typed WS handlers live in frontend registries (`wsEventRegistry` / `wsEventBridge`)
+- Generic RNS Link API over WS (`rns.link.open|identify|request|send|close` and `rns.link.event`) for external tools and plugins. See `docs/en/rns-link-api.md`.
+
+When identity/network is not ready, prefer **503** with a retryable message over opaque **500** for temporary DB/startup failures.
+
+## Frontend conventions
+
+- Vue 3 Options API is the dominant style. Match the file you edit.
+- Routes are hash-based (for example `#/messages`).
+- New top-level pages need: route in `main.js`, nav/tools entry when discoverable, i18n keys, tests.
+- User-visible strings go through locale files (`meshchatx/src/frontend/locales/en.json` at minimum).
+- User-visible action outcomes use `ToastUtils`.
+- Do not use `_`-prefixed keys in Vue `data()` (`vue/no-reserved-keys`).
+- Contribution registries drive nav, tools, commands, settings sections, and WS events. Prefer extending registries over hardcoding one-off shell wiring.
+
+## Android specifics
+
+- UI is a WebView. Backend runs via Chaquopy.
+- File chooser: bare extension tokens like `.identity` are not valid MIME types for `Intent.EXTRA_MIME_TYPES`. Map them to `application/octet-stream` / `*/*`.
+- Set multi-select only when the WebView chooser mode requests it.
+- Storage setup (internal vs external) can create a fresh-looking install if the user picks a different location than previous data.
+- External http(s) links should open in the system browser, not navigate the WebView away from the app.
+
+## Important environment variables and flags
+
+Common overrides (CLI flags usually mirror these):
+
+| Variable / flag | Purpose |
+| ---------------------------------------------------------- | ------------------------------------------------ |
+| `MESHCHAT_HOST` / `--host` | Bind address (default `127.0.0.1`) |
+| `MESHCHAT_PORT` / `--port` | Bind port (default `8000`) |
+| `MESHCHAT_HEADLESS` / `--headless` | Do not auto-launch a browser |
+| `MESHCHAT_STORAGE_DIR` / `--storage-dir` | App storage root |
+| `MESHCHAT_RETICULUM_CONFIG_DIR` / `--reticulum-config-dir` | Reticulum config dir |
+| `MESHCHAT_PUBLIC_DIR` / `--public-dir` | Frontend assets dir |
+| `MESHCHAT_AUTH` / `--auth` | Enable web auth |
+| `MESHCHAT_NO_HTTPS` / `--no-https` | HTTP instead of HTTPS |
+| `MESHCHAT_SSL_CERT` + `MESHCHAT_SSL_KEY` | Custom TLS PEM pair (both required) |
+| `MESHCHAT_IDENTITY_FILE` / `BASE32` / `BASE64` | Seed identity from key material |
+| `MESHCHAT_AUTO_RECOVER` / `--auto-recover` | Attempt DB recovery on startup |
+| `MESHCHAT_EMERGENCY` / `--emergency` | Emergency mode (limited operation) |
+| `MESHCHAT_RESET_PASSWORD` / `--reset-password` | Clear password hash |
+| `MESHCHAT_DISABLE_PLUGINS` / `--disable-plugins` | Disable plugin system |
+| `MESHCHAT_LANDLOCK` | `1` / `0` / unset auto |
+| `MESHCHAT_SELF_CHECK` / `--self-check` | Run diagnostics and exit |
+| `MESHCHAT_MEMORY_DIAG` / `--memory-diag` | tracemalloc diagnostics |
+| `MESHCHAT_DISABLE_CSRF` | Dangerous. Tests/dev only |
+| `MESHCHAT_SKIP_STORAGE_LOCK` | Dangerous. Avoid overlapping instances carefully |
+| `MESHCHAT_RNS_LOG_LEVEL` | RNS log verbosity |
+
+Restore helpers:
+
+```bash
+meshchatx --restore-db /path/to/backup.zip
+```
+
+## Testing expectations
+
+- Backend change → update `tests/backend/`
+- Frontend change → update `tests/frontend/`
+- API contract / route list fixtures may need updates when routes change
+- Prefer focused suites in agent loops. Full `task test` is heavy.
+- Avoid piping long pytest runs through `| tail` in automation shells (can hang the harness).
+- Landlock-enable tests must use a subprocess.
+- Long-running soak / some notification suites can hang. Use timeouts and isolate them unless explicitly requested.
+- Self-check and CI matrices cover cross-platform boot, storage lock fallbacks, and critical HTTP/WS probes. Do not weaken those without cause.
+
+## Licensing and contributions
+
+- Prefer existing per-file SPDX headers. Project-owned files are typically `0BSD`.
+- Upstream-derived files may be MIT or dual-marked. Preserve obligations.
+- Patch-oriented contribution flow is documented in `CONTRIBUTING.md` (LXMF patch submission is first-class for some contributors).
+- Generative AI policy in `CONTRIBUTING.md` requires disclosure and human review. Do not submit unreviewed bulk-generated churn.
+
+## Agent hard rules
+
+1. No emojis in code, markdown, or docs you write for this repo.
+2. No TODO / FIXME noise comments.
+3. No emdashes or semicolons in comments or docs you write.
+4. Do not create markdown docs unless asked (except agent guidance under `docs/agents/` when requested).
+5. Do not commit or push unless the user asks.
+6. Do not generate exploit PoCs, malware, or attack tooling.
+7. Prefer minimal diffs. Match nearby style.
+8. Do not invent install/run flows when Taskfile already covers them.
+9. Mesh-facing designs must pass Zen / architecture gates (`reticulum-zen.md` / `reticulum-design-gates`).
+
+## High-risk change checklist
+
+Before finishing work in these areas, verify the matching invariants:
+
+1. **Identity import / tutorial** - key-only vs zip restore copy is correct, picker accepts real exports, activate-on-finish / skip paths do not orphan imports.
+2. **Conversations / notifications DB** - slim queries, MEMORY temp under Landlock, 503 on retryable SQLite errors.
+3. **Auth / CSRF / WS config** - no new unauthenticated mutating surfaces, no sensitive settings over open WS mutators.
+4. **Plugins** - permissions declared, install preview/consent preserved, signatures/integrity not bypassed.
+5. **Android bridges** - MIME mapping, storage paths, and WebView navigation guards remain correct.
+6. **Identity switch** - no cross-identity leakage via caches, routers, or global singletons.
+7. **Migrations** - schema version bump and upgrade path tested.
+
+## Where to read next
+
+- `docs/agents/conventions/reticulum-zen.md` - Zen of Reticulum hard gates
+- `docs/agents/skills/reticulum-design-gates/SKILL.md` - mesh design checklist
+- `docs/en/architecture.md` - design and process overview
+- `docs/en/identity-and-security.md` - identities, auth, privacy, backups
+- `docs/en/getting-started.md` - UI map and first-run workflow
+- `docs/en/rns-link-api.md` - generic RNS Link WebSocket API
+- `docs/en/platform-guides/linux-sandbox.md` - Firejail / Bubblewrap
+- `docs/en/messaging.md` - LXMF behaviour
+- `CHANGELOG.md` - version-facing behaviour changes
+- `CONTRIBUTING.md` - patch and AI disclosure policy
+
+## Agent guidance index
+
+- `docs/agents/README.md` - index of conventions and skills
+- `docs/agents/conventions/` - surface-specific rules including Reticulum Zen
+- `docs/agents/skills/` - focused workflows including reticulum-design-gates, pages, registries, identity restore/switch, Landlock/SQLite, migrations/backups, auth/CSRF/WS, plugins, RNS Link API, deferred startup, Electron packaging, Android bridge, and test loop
diff --git a/docs/agents/skills/android-webview-bridge/SKILL.md b/docs/agents/skills/android-webview-bridge/SKILL.md
new file mode 100644
index 00000000..8a47f18d
--- /dev/null
+++ b/docs/agents/skills/android-webview-bridge/SKILL.md
@@ -0,0 +1,41 @@
+# Skill: android-webview-bridge
+
+Keep Chaquopy backend boot, WebView file choosers, storage locks, and external navigation correct on Android.
+
+## When to use
+
+- Changing `MainActivity` bridges, file pickers, or storage setup
+- Touching Android Python wrapper / Chaquopy packaging
+- Identity or database restore pickers on Android
+- Debugging empty file pickers or "fresh install" after storage location change
+
+## File chooser
+
+- Extension tokens like `.identity` are **not** valid MIME types for `Intent.EXTRA_MIME_TYPES`.
+- Map `.ext` accepts to `application/octet-stream` and/or `*/*`.
+- Set `EXTRA_ALLOW_MULTIPLE` only when the WebView chooser mode is multi-select.
+
+## Storage and lock
+
+- Internal vs external app storage can look like a fresh install if the user picks a different location than previous data.
+- `fcntl.flock` may be missing. `StorageLock` falls back to a PID soft lock.
+- Stale `.meshchatx.lock` may need clearing in the Chaquopy wrapper path.
+
+## Navigation and packaging
+
+- External http(s) links open in the system browser. Do not navigate the WebView away from the app.
+- Vendored `lxmfy` is synced into Chaquopy `src/main/python/`. Android pip does not install it like desktop setuptools.
+- RNS panic containment matters on Android (see `deferred-network-startup`).
+
+## Key files
+
+- `android/app/src/main/java/com/meshchatx/MainActivity.java`
+- `android/app/src/main/python/meshchat_wrapper.py`
+- `meshchatx/src/frontend/js/rnode/AndroidBridge.js`
+- `docs/agents/conventions/android.md`
+
+## Verification
+
+- Unit / bridge-focused tests if present for the change.
+- Emulator smoke when file chooser, storage, or boot paths change (CI workflow when available).
+- For identity picker changes, also follow `identity-restore`.
diff --git a/docs/agents/skills/auth-csrf-ws-security/SKILL.md b/docs/agents/skills/auth-csrf-ws-security/SKILL.md
new file mode 100644
index 00000000..cfa65621
--- /dev/null
+++ b/docs/agents/skills/auth-csrf-ws-security/SKILL.md
@@ -0,0 +1,53 @@
+# Skill: auth-csrf-ws-security
+
+Keep mutating HTTP behind CSRF and `window.api`. Never move security-boundary settings onto open WebSocket mutators.
+
+## When to use
+
+- Adding POST/PUT/PATCH/DELETE API routes or frontend callers
+- Changing auth, password hash, CSRF, or session cookie behaviour
+- Adding WebSocket message types that mutate state
+- Touching `config.set` or settings that affect the HTTP security boundary
+
+## HTTP rules
+
+- Mutating `/api/v1` calls from the UI must use `window.api` / `apiClient.js` so CSRF headers attach.
+- Raw `fetch(..., { method: "POST" })` against the API fails `tests/frontend/apiFetchGuard.test.js`.
+- Prefer CSRF-protected HTTP for anything that changes auth, passwords, or exposure.
+
+## WebSocket rules
+
+Denylist (must not be set via `config.set` WS):
+
+- `auth_enabled`
+- `auth_password_hash`
+
+When password auth is enabled, WS mutators require an authenticated session. That includes:
+
+- `config.set`
+- `rns.link.open|identify|request|send|close`
+- Nomad download / archive mutators
+- LXMF forwarding rule mutators
+- keyboard shortcut set/delete
+
+Public / read types stay limited. See `WEBSOCKET_PUBLIC_TYPES`, `WEBSOCKET_READ_TYPES`, and `WEBSOCKET_MUTATOR_TYPES` in `websocket_config_guard.py`.
+
+## Dangerous knobs
+
+- `MESHCHAT_DISABLE_CSRF` is tests/dev only. Do not recommend it as a normal fix.
+- Password reset is CLI/env: `--reset-password` / `MESHCHAT_RESET_PASSWORD=true`.
+
+## Key files
+
+- `meshchatx/src/frontend/js/apiClient.js`
+- `meshchatx/src/frontend/js/csrfToken.js`
+- `meshchatx/src/backend/csrf.py`
+- `meshchatx/src/backend/websocket_config_guard.py`
+- `docs/en/identity-and-security.md`
+
+## Verification
+
+```bash
+uv run pytest tests/backend/test_websocket_config_security.py tests/backend/test_websocket_config_guard.py -q --tb=short
+pnpm exec vitest run tests/frontend/apiFetchGuard.test.js
+```
diff --git a/docs/agents/skills/contribution-registries/SKILL.md b/docs/agents/skills/contribution-registries/SKILL.md
new file mode 100644
index 00000000..b3124d34
--- /dev/null
+++ b/docs/agents/skills/contribution-registries/SKILL.md
@@ -0,0 +1,43 @@
+# Skill: contribution-registries
+
+Wire nav, tools, commands, settings search, and WebSocket events through registries instead of hardcoding shell or App.vue dispatch.
+
+## When to use
+
+- Adding a discoverable page, tool, command palette entry, or settings section
+- Adding a new WebSocket event type handled by the UI
+- Plugin contribution points or slot UI
+
+## Registries
+
+| Registry | Role |
+| ----------------------------------------- | -------------------------- |
+| `navRegistry.js` | Primary sidebar / nav |
+| `toolsRegistry.js` | Tools area entries |
+| `commandRegistry.js` | Command palette |
+| `settingsSectionRegistry.js` | Settings search / sections |
+| `wsEventRegistry.js` + `wsEventBridge.js` | Typed WS handlers |
+
+Core boot registers once via `registerCoreContributions.js` and `core*Entries.js` siblings.
+
+## Hard rules
+
+- New top-level pages still need a route in `main.js` (see `page-toast-tests`). Registries cover discoverability and dispatch, not routing alone.
+- Prefer `onWsEvent` / registry handlers over growing ad-hoc `switch (json.type)` blocks in `App.vue`.
+- Settings search keywords belong in the settings section registry, not scattered only inside `SettingsPage.vue`.
+- Plugin UI uses the existing slot vocabulary (`PluginSlotNode` / related renderers). Do not invent a parallel slot system.
+
+## Key files
+
+- `meshchatx/src/frontend/js/registries/`
+- `meshchatx/src/frontend/js/registries/registerCoreContributions.js`
+- `meshchatx/src/frontend/components/plugins/PluginSlotNode.vue`
+- `meshchatx/src/frontend/main.js` (routes)
+
+## Verification
+
+```bash
+pnpm exec vitest run tests/frontend/ -t registry
+```
+
+If no dedicated registry tests match, run the page or App tests that cover the new entry, plus eslint on touched files.
diff --git a/docs/agents/skills/database-migrations-backups/SKILL.md b/docs/agents/skills/database-migrations-backups/SKILL.md
new file mode 100644
index 00000000..f51d6b03
--- /dev/null
+++ b/docs/agents/skills/database-migrations-backups/SKILL.md
@@ -0,0 +1,44 @@
+# Skill: database-migrations-backups
+
+Bump schema versions correctly, keep backups and snapshots safe, and never conflate identity-key restore with full database zip restore.
+
+## When to use
+
+- Changing SQLite schema or migrations
+- Touching backup, snapshot, restore, or crash-recovery paths
+- Adding tables / columns used by conversation or settings features
+
+## Schema rules
+
+- Engine is SQLite with explicit SQL. No ORM.
+- Bump `LATEST_VERSION` in `meshchatx/src/backend/database/schema.py` and add a migration path.
+- Test upgrade from an older version when the change is non-trivial.
+
+## Backup and snapshot rules
+
+- Backups skip `database-backups/` and `snapshots/` so a new zip does not nest itself (`BACKUP_SKIP_DIR_NAMES`).
+- Suspicious shrink writes `backup-SUSPICIOUS-*.zip` and skips rotation. Do not treat that as a normal backup.
+- Checkpoint WAL before zip snapshots when the live DB is open.
+- Worker-thread connections must share `DatabaseProvider` pragmas (see `landlock-sqlite`).
+
+## Two restore operations
+
+| Goal | API / CLI | Artifact |
+| ---------------------------------- | ----------------------------------------------- | --------------------------- |
+| Private key only | `POST /api/v1/identity/restore` | identity key bytes / `.bin` |
+| History + settings + identity tree | `POST /api/v1/database/restore`, `--restore-db` | `.zip` |
+
+Details for pickers and tutorial copy: `identity-restore`.
+
+## Key files
+
+- `meshchatx/src/backend/database/schema.py`
+- `meshchatx/src/backend/database/__init__.py`
+- `meshchatx/meshchat.py` (backup / restore routes, `prepare_for_database_restore`)
+- `electron/offlineRecovery.js`
+
+## Verification
+
+```bash
+uv run pytest tests/backend/test_database_snapshots.py tests/backend/test_schema_migration_upgrade.py -q --tb=short
+```
diff --git a/docs/agents/skills/deferred-network-startup/SKILL.md b/docs/agents/skills/deferred-network-startup/SKILL.md
new file mode 100644
index 00000000..5c06261b
--- /dev/null
+++ b/docs/agents/skills/deferred-network-startup/SKILL.md
@@ -0,0 +1,45 @@
+# Skill: deferred-network-startup
+
+Treat HTTP-up as distinct from RNS-ready. Gate UI on `/api/v1/status`, return 503 for retryable init failures, and contain RNS panic on Android.
+
+## When to use
+
+- Changing boot order, status payload, or loading screens
+- Adding APIs that need identity / DB / RNS before answering
+- Touching Electron loading probes or Android Chaquopy boot
+- Debugging "app loads but mesh is dead" or early 500s during start
+
+## Lifecycle facts
+
+- HTTP can bind before RNS / identity finish.
+- `/api/v1/status` reports `starting` / `ok` / `failed` with `stage` and `network_ready`.
+- `starting` is normal, not an error.
+- Electron loading probes accept HTTP 200 with `starting` or `ok`. Do not require `network_ready` before first navigation.
+- Vue boot uses startup interpreters that can mount recovery UI when `failed` still allows degraded UI.
+
+## API behaviour
+
+- Prefer **503** with a retryable message when identity / DB / network is temporarily unavailable.
+- Prefer opaque **500** only for unexpected failures after ready.
+- CLI one-shots (`--self-check`, restore helpers) may still initialize synchronously.
+
+## Android / RNS panic
+
+- `RNS.panic()` must be contained (`rns_startup_recovery.py`). Uncaught `os._exit` kills the in-process Android Python host.
+- Off-main-thread RNS init cannot register signals. Reinstall handlers on the main loop after ready.
+
+## Key files
+
+- `meshchatx/meshchat.py` (`/api/v1/status`, background RNS init)
+- `meshchatx/src/frontend/js/networkStartupWait.js`
+- `electron/loadingStatusProbe.js`
+- `meshchatx/src/backend/rns_startup_recovery.py`
+- `meshchatx/src/backend/reticulum_config_guard.py`
+
+## Verification
+
+```bash
+uv run pytest tests/backend/test_rns_startup_recovery.py -q --tb=short
+pnpm exec vitest run tests/frontend/networkStartupWait.test.js
+uv run python -m meshchatx.meshchat --self-check
+```
diff --git a/docs/agents/skills/electron-frozen-packaging/SKILL.md b/docs/agents/skills/electron-frozen-packaging/SKILL.md
new file mode 100644
index 00000000..2ff163f7
--- /dev/null
+++ b/docs/agents/skills/electron-frozen-packaging/SKILL.md
@@ -0,0 +1,46 @@
+# Skill: electron-frozen-packaging
+
+Package and recover the desktop shell correctly: frozen subprocess re-entry, loading probes, crash/offline DB restore, and external URL guards.
+
+## When to use
+
+- Changing Electron main process, backend spawn, or close / tray behaviour
+- Spawning bots, rnsh, LXMFy, or other Python helpers from a packaged build
+- Touching crash screens, offline recovery, or external link opening
+
+## Frozen executable rules
+
+- In frozen builds, `sys.executable` **is** MeshChatX.
+- Never spawn `python -m …` for bots, rnsh, or LXMFy from the packaged app.
+- Use `--meshchatx-run-module <module>` so helpers re-enter the same binary without launching a second full app (storage lock collision).
+
+## Loading and navigation
+
+- Loading shell probes `/api/v1/status`. `starting` is valid for early navigation (see `deferred-network-startup`).
+- `will-navigate` / `safeExternalUrl` send http(s) to the OS browser. Do not replace the app window with external sites.
+- Close behaviour (quit / tray / ask) persists per user choice. Guard re-entrancy on close.
+
+## Crash / offline recovery
+
+- Crash UI can list backups under `database-backups/` and `snapshots/`.
+- Prefer newest non-`SUSPICIOUS` backup.
+- Relaunch paths may pass `--auto-recover` / `--emergency`.
+
+## Key files
+
+- `electron/main.js`
+- `electron/backendProcess.js`
+- `electron/loadingStatusProbe.js`
+- `electron/offlineRecovery.js`
+- `electron/closeBehavior.js`
+- `electron/safeExternalUrl.js`
+- `meshchatx/meshchat.py` (`--meshchatx-run-module`)
+
+## Verification
+
+```bash
+uv run pytest tests/backend/test_meshchatx_run_module.py -q --tb=short
+pnpm exec vitest run tests/electron/ --passWithNoTests 2>/dev/null || true
+```
+
+Prefer focused Electron unit tests under `tests/electron/` when present for the touched module.
diff --git a/docs/agents/skills/identity-restore/SKILL.md b/docs/agents/skills/identity-restore/SKILL.md
new file mode 100644
index 00000000..49624f3b
--- /dev/null
+++ b/docs/agents/skills/identity-restore/SKILL.md
@@ -0,0 +1,44 @@
+# Skill: identity-restore
+
+Identity key import vs database zip restore, tutorial and Android pickers.
+
+# MeshChatX Identity Restore
+
+## Two different restores
+
+| Goal | UI | API / artifact |
+| -------------------- | ---------------------------------- | ---------------------------------------- |
+| Identity private key | Tutorial step 2, Identities import | `POST /api/v1/identity/restore` |
+| LXMF + settings + DB | About → Restore from File | `POST /api/v1/database/restore` (`.zip`) |
+
+Never imply identity-key import restores message history.
+
+## Guards checklist
+
+- File picker `accept`: `.bin,.key,.identity,application/octet-stream,*/*`
+- Export download filename: `identity.bin`
+- Reject empty / oversized identity payloads (client + server, max 64 KiB)
+- Normalize base32 by stripping all whitespace
+- Multipart field order must not matter
+- `ValueError` → HTTP 400
+- Re-import must preserve existing metadata (icons/addresses)
+- Tutorial: import on Continue, activate on Finish. Split switch vs delete failures.
+- Tutorial skip/abandon with pending import: confirm activate or warn
+- IdentitiesPage: keep modal open during restore, toast errors, offer switch after success
+- Android: map extension accepts to MIME types in `MainActivity`
+
+## Tests to update
+
+- `tests/frontend/TutorialModalMigration.test.js`
+- `tests/frontend/IdentitiesPage.test.js`
+- `tests/backend/test_identity_restore.py`
+- `tests/backend/test_identity_restore_http_api.py`
+
+## Key files
+
+- `meshchatx/src/frontend/components/TutorialModal.vue`
+- `meshchatx/src/frontend/components/settings/IdentitiesPage.vue`
+- `meshchatx/src/frontend/components/about/AboutPage.vue`
+- `meshchatx/src/backend/identity_manager.py`
+- `meshchatx/meshchat.py` (identity backup/restore routes)
+- `android/.../MainActivity.java`
diff --git a/docs/agents/skills/identity-switch-teardown/SKILL.md b/docs/agents/skills/identity-switch-teardown/SKILL.md
new file mode 100644
index 00000000..ad7d5896
--- /dev/null
+++ b/docs/agents/skills/identity-switch-teardown/SKILL.md
@@ -0,0 +1,43 @@
+# Skill: identity-switch-teardown
+
+Switch identities by tearing down the full `IdentityContext` and clearing frontend caches so routers and managers never leak cross-identity state.
+
+## When to use
+
+- Changing identity create / switch / delete / activate flows
+- Adding managers that hold RNS destinations, bots, timers, or DB handles
+- Caching peer lists, favourites, or conversation state in process globals or Vue stores
+
+## Model
+
+- One active `IdentityContext` at a time
+- Per-identity data under `storage/identities/<hash>/`
+- Shared Reticulum config under `~/.reticulum` (does **not** reset on switch)
+
+## Hard rules
+
+- Do not stash identity-specific state in process globals.
+- Teardown must deregister RNS handlers, stop bots / RRC / RNSH / forwarding, close LXMRouter destinations, and shut down DB connections (`IdentityContext.teardown()`).
+- After switch, prefer a controlled reload / clear of frontend caches over partial UI patches that leave stale WS subscriptions.
+- Favourites layout, snapshots, SSL certs, and LXMF dirs are per-identity. Do not write them into shared storage roots.
+
+## Related but different
+
+Identity **key** import vs database **zip** restore is covered by `identity-restore`. This skill is about live switch / teardown correctness.
+
+## Key files
+
+- `meshchatx/src/backend/identity_context.py`
+- `meshchatx/src/backend/identity_manager.py`
+- `meshchatx/meshchat.py` (switch endpoints, `identity_switched` broadcast)
+- `meshchatx/src/frontend/components/App.vue` (`identity_switched` handler)
+- `meshchatx/src/frontend/components/settings/IdentitiesPage.vue`
+
+## Verification
+
+```bash
+uv run pytest tests/backend/test_identity_restore.py tests/backend/test_identity_restore_http_api.py -q --tb=short
+pnpm exec vitest run tests/frontend/IdentitiesPage.test.js
+```
+
+When adding a new manager, add teardown coverage or assert it is stopped from `IdentityContext.teardown()`.
diff --git a/docs/agents/skills/landlock-sqlite/SKILL.md b/docs/agents/skills/landlock-sqlite/SKILL.md
new file mode 100644
index 00000000..3651f37b
--- /dev/null
+++ b/docs/agents/skills/landlock-sqlite/SKILL.md
@@ -0,0 +1,43 @@
+# Skill: landlock-sqlite
+
+Landlock + SQLite conversation-load failures (temp_store, slim queries, memory pressure).
+
+# MeshChatX Landlock + SQLite
+
+## Symptoms
+
+- `/api/v1/lxmf/conversations` or `/api/v1/notifications` return 500/503
+- Logs show `sqlite3.OperationalError: unable to open database file`
+- Happens after Landlock enables, often with large message `fields` / base64 blobs
+
+## Root causes (priority order)
+
+1. Worker-thread connections missing `PRAGMA temp_store=MEMORY` (`DatabaseProvider._configure_connection`)
+2. Conversation SELECT pulling full `content` / `fields`
+3. Memory-pressure switching to `temp_store=FILE` under Landlock
+4. Identity context not ready (should be 503, not 500)
+
+## Required behavior
+
+- Default: `temp_store=MEMORY` on every new connection
+- Landlock active + memory pressure: shrink cache/mmap, **keep MEMORY temp**
+- Non-Landlock memory pressure may use FILE temp + storage-local `sqlite-tmp` TMPDIR
+- List queries: `substr(content, 1, 240)` and SQL `instr` flags for attachments
+- API: map OperationalError / unable-to-open / locked to **503** with retryable message
+
+## Verification
+
+```bash
+uv run pytest tests/backend/test_sqlite_landlock_temp_store.py tests/backend/test_sqlite_memory_pressure.py tests/backend/test_landlock_sandbox.py -q
+```
+
+For live stress, run Landlock in a **subprocess** (sandbox applies once per process). Expect FILE temp complex queries to fail under Landlock. MEMORY must pass.
+
+## Key files
+
+- `meshchatx/src/backend/database/provider.py`
+- `meshchatx/src/backend/database/__init__.py`
+- `meshchatx/src/backend/memory_pressure.py`
+- `meshchatx/src/backend/message_handler.py`
+- `meshchatx/src/backend/landlock_sandbox.py`
+- `meshchatx/meshchat.py` (conversations/notifications error mapping)
diff --git a/docs/agents/skills/page-toast-tests/SKILL.md b/docs/agents/skills/page-toast-tests/SKILL.md
new file mode 100644
index 00000000..391cd97a
--- /dev/null
+++ b/docs/agents/skills/page-toast-tests/SKILL.md
@@ -0,0 +1,133 @@
+# Skill: page-toast-tests
+
+New MeshChatX pages with routes, nav, toasts, i18n, and tests.
+
+# MeshChatX Page + Toast + Tests
+
+## Purpose
+
+Use this skill to implement feature pages in this repository without missing integration points:
+
+- frontend route registration
+- navigation exposure
+- translated labels
+- user feedback via `ToastUtils`
+- test coverage updates
+
+This skill is optimized for the MeshChatX structure under `meshchatx/src/frontend` and `tests/`.
+
+## Quick Decisions
+
+Before editing files, decide:
+
+1. Is this a top-level page route or a modal/section inside an existing page?
+2. Does the action require backend API work, or can it stay frontend-only?
+3. Which toast types are expected on success, warning, and failure?
+4. Which tests should prove behavior: frontend unit test, backend test, or both?
+
+## Required Integration Points
+
+For a new top-level page, verify all relevant items:
+
+- Add route in `meshchatx/src/frontend/main.js` with `defineAsyncComponent`.
+- Add sidebar/tools entry in `meshchatx/src/frontend/components/App.vue` when the page must be user-discoverable.
+- Add translation keys in `meshchatx/src/frontend/locales/en.json` and other maintained locale files when touched by task scope.
+- Use `ToastUtils` in page actions that save, submit, refresh, copy, or fail.
+- Add or update tests in `tests/frontend/*.test.js`.
+- Add or update backend tests in `tests/backend/*.py` if API behavior changes.
+
+## Page Creation Workflow
+
+### 1) Create the page component
+
+Place the component in the matching feature directory, for example:
+
+- `meshchatx/src/frontend/components/tools/<NewPage>.vue`
+- `meshchatx/src/frontend/components/<feature>/<NewPage>.vue`
+
+Keep the page consistent with existing patterns:
+
+- use translated UI text with `$t("...")`
+- use `window.api` for API calls in page logic
+- use `MaterialDesignIcon` patterns already used in peer pages
+
+### 2) Register route
+
+In `meshchatx/src/frontend/main.js`:
+
+- add a route object with stable `name` and `path`
+- load component via `defineAsyncComponent(() => import("..."))`
+- use `props: true` only when path/query data is required by the component
+
+### 3) Surface navigation
+
+If user navigation should expose the page:
+
+- add a `SidebarLink` entry in `meshchatx/src/frontend/components/App.vue`, or
+- add it in the tools area if it belongs under tools, not primary nav
+
+Keep naming consistent between route name, i18n label, and visible button/link text.
+
+## Toast Conventions
+
+Import from:
+
+- `meshchatx/src/frontend/js/ToastUtils.js`
+
+Use:
+
+- `ToastUtils.success(message)` for completion
+- `ToastUtils.error(message)` for failures
+- `ToastUtils.warning(message)` for recoverable risk
+- `ToastUtils.info(message)` for neutral updates
+- `ToastUtils.loading(message, 0, key)` and `ToastUtils.dismiss(key)` for long-running operations
+
+Guidelines:
+
+- prefer translated messages from locale keys over hardcoded strings
+- include backend-provided error detail when safe and useful
+- for progress toasts, use stable keys to avoid stacking duplicates
+
+## Test Workflow
+
+### Frontend tests (`vitest` + `@vue/test-utils`)
+
+When adding page behavior:
+
+- create or extend a test in `tests/frontend/`
+- mount component with `$t`, `$route`, `$router` mocks
+- stub non-essential child components
+- mock `window.api` responses for success and error flows
+- assert both state and rendered output
+- assert toast calls when operation outcomes are user-visible
+
+### Backend tests (`pytest`)
+
+When API/backend behavior is changed:
+
+- add focused tests under `tests/backend/`
+- patch heavy dependencies and network side effects
+- verify returned payload shape and error contracts expected by frontend
+- keep fixture setup minimal and local to behavior under test
+
+## Done Checklist
+
+Only finish once these are true:
+
+- route works and page renders from navigation path
+- all user-facing strings are translated keys
+- toast behavior exists for core success/failure actions
+- frontend test covers key path and an error path
+- backend tests are updated if API behavior changed
+- no unrelated files were changed
+
+## Quality Bar
+
+- Follow existing file and naming conventions before introducing new patterns.
+- Keep implementation incremental. Avoid broad refactors in feature delivery.
+- Prefer clear user feedback over silent failures.
+- Match current test style in nearby files instead of inventing a new structure.
+
+## Additional Resources
+
+- Trigger and output examples: [examples.md](examples.md)
diff --git a/docs/agents/skills/page-toast-tests/examples.md b/docs/agents/skills/page-toast-tests/examples.md
new file mode 100644
index 00000000..addeaf15
--- /dev/null
+++ b/docs/agents/skills/page-toast-tests/examples.md
@@ -0,0 +1,20 @@
+# Example Triggers
+
+Use this skill when user requests resemble:
+
+- "Create a new tools page for X."
+- "Add a page and wire it into navigation."
+- "Add toasts for save and error states."
+- "Add tests for this new page flow."
+- "Add frontend and backend coverage for this feature."
+
+# Example Outcomes
+
+Typical outputs from this skill:
+
+- new page component in `meshchatx/src/frontend/components/...`
+- route registration in `meshchatx/src/frontend/main.js`
+- navigation entry in `meshchatx/src/frontend/components/App.vue` when needed
+- locale keys in `meshchatx/src/frontend/locales/*.json`
+- toast usage through `meshchatx/src/frontend/js/ToastUtils.js`
+- frontend and backend test updates in `tests/frontend/` and `tests/backend/`
diff --git a/docs/agents/skills/plugin-install-security/SKILL.md b/docs/agents/skills/plugin-install-security/SKILL.md
new file mode 100644
index 00000000..fd7cd39b
--- /dev/null
+++ b/docs/agents/skills/plugin-install-security/SKILL.md
@@ -0,0 +1,55 @@
+# Skill: plugin-install-security
+
+Install, sign, permission-grant, and sandbox plugins without bypassing RSG, integrity, or runtime guards.
+
+## When to use
+
+- Adding or changing plugin install / enable / invoke flows
+- Declaring new hooks or manager capabilities
+- Touching WASM, Python, or Sideband plugin runtimes
+- Debugging "permission denied", signature failures, or silent disable after tamper
+
+## Threat model (short)
+
+Plugins are powerful. Treat install and enable as security-sensitive.
+
+| Runtime | Risk | Notes |
+| ------------------------- | ------ | ----------------------------------------- |
+| Frontend Worker | Medium | Capability grants, isolated storage modes |
+| Backend WASM | Medium | wasmtime fuel / capability gates |
+| Backend Python / Sideband | High | Explicit danger / permission gating |
+
+## Required flow
+
+1. Preview install (permissions, endpoints, signature status)
+2. User consent on declared permissions / network endpoints
+3. Enable only after grants are stored
+4. Runtime enforces declared + granted hooks / managers / storage / `network:fetch`
+5. Integrity hashing after install. Tampered trees disable, they do not silently run
+
+## Hard rules
+
+- Invalid RSG signatures **hard-block** install. Do not add bypass paths.
+- ZIP extract must use zip-slip safe extraction. WASM must pass `validate_wasm_file`.
+- New hooks go in `KNOWN_HOOKS`. New managers go in `KNOWN_MANAGERS` in `plugin_permissions.py`.
+- Plugin i18n lives in the plugin bundle (`locales/{locale}.json`), not core `en.json`.
+- Disable everything with `--disable-plugins` / `MESHCHAT_DISABLE_PLUGINS=true` when diagnosing.
+
+## Key files
+
+- `meshchatx/src/backend/plugin_manager.py`
+- `meshchatx/src/backend/plugin_guard.py`
+- `meshchatx/src/backend/plugin_permissions.py`
+- `meshchatx/src/backend/plugin_signature.py`
+- `meshchatx/src/backend/plugin_integrity.py`
+- `meshchatx/src/backend/plugin_python_runtime.py`
+- `meshchatx/src/frontend/js/plugins/pluginWorker.js`
+- `meshchatx/src/backend/data/plugins/mcx-bugs/` (reference plugin)
+
+## Verification
+
+```bash
+uv run pytest tests/backend/test_plugin_manager.py tests/backend/test_plugin_permissions.py tests/backend/test_plugin_signature.py tests/backend/test_plugin_integrity.py tests/backend/test_plugin_security.py -q --tb=short
+```
+
+Add focused coverage when changing grant normalization, network endpoint scanning, or invoke paths.
diff --git a/docs/agents/skills/reticulum-design-gates/SKILL.md b/docs/agents/skills/reticulum-design-gates/SKILL.md
new file mode 100644
index 00000000..afb53758
--- /dev/null
+++ b/docs/agents/skills/reticulum-design-gates/SKILL.md
@@ -0,0 +1,100 @@
+# Skill: reticulum-design-gates
+
+Stop IP-era and cloud-era design mistakes before they land in MeshChatX.
+Grounded in the [Zen of Reticulum](https://reticulum.network/manual/zen.html) and MeshChatX architecture.
+
+## When to use
+
+- Any feature that sends, receives, discovers, or stores mesh data
+- New aspects, destinations, announces, links, or propagation behaviour
+- Plugins that talk to the mesh
+- Bug-report / telemetry / logging paths that might leak identity material
+- "Quick" integrations that want HTTP, DNS, Firebase, or a central API "just for sync"
+
+Also read: `docs/agents/conventions/reticulum-zen.md`, `docs/agents/overview.md`, `docs/en/architecture.md`.
+
+## Gate 0: Intent
+
+State in one sentence what the user can do offline on a LoRa-only mesh after this change.
+If you cannot, you are probably designing a cloud client.
+
+## Gate 1: No center
+
+Reject designs that need any of:
+
+- A mandatory cloud backend, CDN, or phone-home license server
+- A global name registry or "default discovery server" for core reachability
+- A privileged mesh node role that can read plaintext or revoke peers by policy
+
+Allowed:
+
+- Optional clearnet helpers behind privacy mode / explicit settings (docs fetch, community interface lists)
+- Local HTTPS UI to the user's own MeshChatX process
+- User-chosen propagation nodes and hubs
+
+## Gate 2: Identity is a hash
+
+- Peers are destination hashes (and related identity hashes), not IPs or hostnames.
+- UI may show local display names. Those names are labels, not network addresses.
+- Do not invent a new addressing scheme when RNS destinations + aspects already fit.
+- Custom apps get their own aspect (example `mcx-bugs-v1`). Do not overload `lxmf.delivery` for non-LXMF traffic.
+
+## Gate 3: Hostile medium
+
+- Do not add plaintext mesh channels for convenience.
+- Do not log private keys, full session secrets, or unredacted message bodies by default.
+- Bug reports and diagnostics must default to redaction (hashes, paths, IPs, URLs, emails, display names).
+- Plugin permissions stay capability-gated. No silent full-host mesh access for "examples".
+
+## Gate 4: Scarcity and async
+
+- Prefer event/handler and store-and-forward over blocking request/response UIs.
+- Missing path: request path, allow propagate, surface recoverable error. Do not spin forever.
+- Keep list APIs and announces slim. Do not ship multi-MB blobs in conversation lists.
+- Large files use RNCP / attachments / explicit transfer tools, not chat text fields.
+
+## Gate 5: Transport agnostic
+
+- Application code uses RNS Destination / Link / Packet / LXMF APIs.
+- Do not special-case WiFi vs LoRa vs TCP inside feature managers unless the feature is literally interface configuration.
+- Interface selection belongs in Reticulum config and MeshChatX interface settings, not in message send hot paths.
+
+## Gate 6: MeshChatX architecture fit
+
+- Business rules in `meshchatx/src/backend/` managers, not dumped into `meshchat.py` routes.
+- Identity-scoped state under `IdentityContext`. Switch must tear down cleanly.
+- HTTP `/api/v1/*` for local UI. Mesh peers do not become REST clients of MeshChatX.
+- New pages: route, nav/tools registry, i18n, toasts, tests (see `page-toast-tests`).
+- New plugin managers/hooks: update `KNOWN_MANAGERS` / `KNOWN_HOOKS` and permission locale strings.
+
+## Anti-patterns (do not ship)
+
+| Anti-pattern | Do this instead |
+| ---------------------------------------------------- | ---------------------------------------------------------- |
+| `fetch('https://api...')` required to send a message | LXMF send via local router |
+| Store peer as `host:port` | Store destination hash + aspect |
+| Spinner until ACK or fail hard | Outbound state machine + propagation |
+| JSON status blob every second on LoRa | Announce sparingly, encode intent densely |
+| Global singleton cache of all identities' inboxes | Per-identity DB and managers |
+| New mesh app on `lxmf.delivery` | Dedicated aspect + link/request or LXMF only if it is mail |
+| Debug dump with private key paths and full hashes | Redacted export with user toggles |
+
+## Review checklist (paste into PR / finish notes)
+
+- [ ] Works with clearnet disabled / privacy mode on for mesh-critical paths
+- [ ] Addresses destination hash + aspect
+- [ ] Survives delay, missing path, and identity switch
+- [ ] Payload size justified for constrained links
+- [ ] No new unauthenticated mutating HTTP/WS surface
+- [ ] No cross-identity leakage
+- [ ] Tests cover success and recoverable failure
+
+## Key references
+
+- https://reticulum.network/manual/zen.html
+- `docs/agents/conventions/reticulum-zen.md`
+- `docs/agents/overview.md`
+- `docs/en/architecture.md`
+- `docs/en/messaging.md`
+- `docs/en/rns-link-api.md`
+- `docs/en/identity-and-security.md`
diff --git a/docs/agents/skills/rns-link-api/SKILL.md b/docs/agents/skills/rns-link-api/SKILL.md
new file mode 100644
index 00000000..6ac819bf
--- /dev/null
+++ b/docs/agents/skills/rns-link-api/SKILL.md
@@ -0,0 +1,44 @@
+# Skill: rns-link-api
+
+Implement or consume the generic RNS Link WebSocket transport and plugin manager capabilities without breaking auth, caching, or disconnect cleanup.
+
+## When to use
+
+- Changing `rns.link.*` WebSocket handlers or `RnsLinkManager`
+- Exposing link open/request/send to plugins
+- Building external tools that treat MeshChatX as an RNS transport
+- Debugging stuck opens, leaked links, or auth failures on link mutators
+
+## Protocol (summary)
+
+Client → server types: `rns.link.open|identify|request|send|close` with `request_id`.
+
+- `aspect` is dot-separated RNS app name + sub-aspects (example `microrn.mgmt`).
+- Payloads are msgpack, base64-encoded (`data_b64`, `payload_b64`, `body_b64`).
+- Links are cached per `(aspect, destination_hash)`. `close` tears down and uncaches.
+- In-flight `open` / `request` tasks cancel when that WebSocket client disconnects.
+- Server replies reuse the same `type` with `status` of `phase` / `progress` / `success` / `failure`.
+- Broadcasts: `rns.link.event` (`packet_received`, `link_closed`).
+
+Full table: `docs/en/rns-link-api.md`.
+
+## Auth and plugins
+
+- When password auth is enabled, all `rns.link.*` client messages require an authenticated session (same as other WS mutators). See `auth-csrf-ws-security`.
+- Plugins need `permissions.managers` entries such as `rnsLink.open` and optional `hooks: ["rns.link.event"]`.
+- Invoke via `POST /api/v1/plugins/{id}/invoke` with `method: "callManager"`.
+- New manager names must be added to `KNOWN_MANAGERS` (see `plugin-install-security`).
+
+## Key files
+
+- `docs/en/rns-link-api.md`
+- `meshchatx/src/backend/rns_link_manager.py`
+- `meshchatx/meshchat.py` (WS dispatch, per-client task tracking)
+- `meshchatx/src/backend/plugin_manager.py`
+- `meshchatx/src/backend/websocket_config_guard.py`
+
+## Verification
+
+```bash
+uv run pytest tests/backend/test_rns_link_manager.py tests/backend/test_rns_link_plugin.py -q --tb=short
+```
diff --git a/docs/agents/skills/test-loop/SKILL.md b/docs/agents/skills/test-loop/SKILL.md
new file mode 100644
index 00000000..82f0a989
--- /dev/null
+++ b/docs/agents/skills/test-loop/SKILL.md
@@ -0,0 +1,46 @@
+# Skill: test-loop
+
+Focused verification with task/uv/pnpm without hanging shells.
+
+# MeshChatX Test Loop
+
+## Default order
+
+1. Lint/format only the touched surface if needed
+2. Focused unit tests for changed files
+3. Broader suite only if asked or if cross-cutting
+
+## Preferred commands
+
+```bash
+# Backend focused
+uv run pytest tests/backend/test_<name>.py -q --tb=short
+
+# Frontend focused
+pnpm exec vitest run tests/frontend/<Name>.test.js
+
+# Quick regression
+task test:quick
+
+# Broader
+task test:backend
+task test:frontend
+```
+
+## Anti-hang rules
+
+- Do not pipe long pytest runs through `| tail` in agent shells (blocks until process ends).
+- Prefer `--tb=short` / `-q` and explicit file lists.
+- Skip or isolate `long_running` / notification soak tests unless explicitly requested.
+- Landlock apply tests: always subprocess.
+
+## After UI edits
+
+```bash
+pnpm exec eslint <changed.vue> --fix
+pnpm exec vitest run tests/frontend/<related>.test.js
+```
+
+## After identity / Landlock edits
+
+Run the matching skill's verification section before claiming done.
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────